﻿using GetThis_Server.Packets;
using GetThis_Server.Users;
using Server;
using Server.Packets;
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Net;
using System.Text;

namespace GetThis_Server
{
    internal class AuthenticationManager
    {
        #region Members

        /// <summary>
        /// Random number generator.
        /// </summary>
        private static readonly Random m_Random = new Random();

        /// <summary>
        /// The related <see cref="T:Server.TcpClientManager" />.
        /// </summary>
        private readonly TcpClientManager m_clientManager;

        private readonly Data m_data;

        /// <summary>
        /// User name.
        /// </summary>
        private string m_userName;

        /// <summary>
        /// The key sent to the user.
        /// </summary>
        private int m_key;

        #endregion

        #region Constructors

        static AuthenticationManager()
        {
            Packet.Define(new C1HandshakePacket(), new S2RandomNumberPacket(), new C3EncryptedPacket(), new S4AuthorizationPacket());
        }

        internal AuthenticationManager(TcpClientManager clientManager, Data data)
        {
            Contract.Requires<ArgumentNullException>(clientManager != null);
            Contract.Requires<ArgumentNullException>(data != null);

            m_clientManager = clientManager;
            m_data = data;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Define the invariant valid state for this class.
        /// </summary>
        [ContractInvariantMethod]
        private void InvariantValidState()
        {
            Contract.Invariant(m_clientManager != null);
            Contract.Invariant(m_data != null);
        }

        internal void Handle(Packet packet)
        {
            Contract.Requires<ArgumentNullException>(packet != null);

            throw new NotImplementedException(String.Format("The packet Type {0} handle has not been implemented yet.", packet.GetType().ToString()));
        }

        internal void Handle(C1HandshakePacket c1handshakePacket)
        {
            Contract.Requires<ArgumentNullException>(c1handshakePacket != null);
            Contract.Assert(m_clientManager != null);

            m_userName = c1handshakePacket.UserName;
            m_key = m_Random.Next(Int32.MaxValue);
            m_clientManager.Server.SendPacket(m_clientManager, new S2RandomNumberPacket((uint)m_key));
        }

        internal void Handle(C3EncryptedPacket c3encryptedPacket)
        {
            Contract.Requires<ArgumentNullException>(c3encryptedPacket != null);
            Contract.Assert(m_clientManager != null);

            // empty data
            var user = m_data.User;
            if (user == null)
            {
                m_clientManager.Server.SendPacket(m_clientManager, new S4AuthorizationPacket(S4AuthorizationPacket.Status.None));
                return;
            }

            // user not found
            var userFound = user.FindByName(m_userName);
            if (userFound == null)
            {
                m_clientManager.Server.SendPacket(m_clientManager, new S4AuthorizationPacket(S4AuthorizationPacket.Status.UserNotFound));
                return;
            }

            // invalid data
            if (String.IsNullOrWhiteSpace(userFound.Ip) || String.IsNullOrWhiteSpace(userFound.Key) || userFound.AuthorizationDuration.TotalSeconds <= 0)
            {
                m_clientManager.Server.SendPacket(m_clientManager, new S4AuthorizationPacket(S4AuthorizationPacket.Status.None));
                return;
            }

            // invalid ip
            IPAddress userAddress;
            IPAddress[] hostAddresses;
            if (!userFound.BypassIp && ((hostAddresses = Dns.GetHostAddresses(userFound.Ip)).Length == 0 || !IPAddress.TryParse(c3encryptedPacket.UserIp, out userAddress) || !IPAddress.Equals(userAddress, hostAddresses[0])))
            {
                m_clientManager.Server.SendPacket(m_clientManager, new S4AuthorizationPacket(S4AuthorizationPacket.Status.InvalidIp));
                return;
            }

            // invalid key
            if (!userFound.BypassKey && Encrypt(userFound.Key, m_key) != c3encryptedPacket.UserKey)
            {
                m_clientManager.Server.SendPacket(m_clientManager, new S4AuthorizationPacket(S4AuthorizationPacket.Status.InvalidKey));
                return;
            }

            // authorization granted
            while (String.IsNullOrWhiteSpace(userFound.Key = Path.GetRandomFileName()));
            m_clientManager.Server.SendPacket(m_clientManager, new S4AuthorizationPacket(S4AuthorizationPacket.Status.Accepted, userFound.Key, userFound.AuthorizationDuration.TotalSeconds));
            m_data.TrySave();
        }

        private string Encrypt(string text, int key)
        {
            Contract.Requires<ArgumentNullException>(text != null);

            var keyBites = BitConverter.GetBytes(key);
            int textlength = text.Length;
            int keylength = keyBites.Length;
            char[] chars = new char[textlength];
            byte[] bytes = new byte[textlength];
            StringBuilder result = new StringBuilder(textlength);
            for (int x = 0; x < textlength; x++)
            {
                chars[x] = (char)(text[x] ^ keyBites[x % keylength]);
                bytes[x] = (byte)(char)(text[x] ^ keyBites[x % keylength]);
                result.Append((char)(text[x] ^ keyBites[x % keylength]));
            }
            return result.ToString();
        }

        #endregion
    }
}
